package com.danbay.cloud.common.service;

import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
import com.danbay.cloud.base.constants.SmsType;
import com.danbay.cloud.common.api.dto.CodeValidateDto;
import com.danbay.cloud.common.api.dto.sms.*;
import com.danbay.cloud.common.api.service.ISmsService;
import com.danbay.cloud.common.config.SmsProperties;
import com.danbay.framework.exception.BusinessException;
import com.danbay.framework.redis.RedisClient;
import com.danbay.framework.utils.BeanValidator;
import com.danbay.framework.utils.DateUtils;
import com.danbay.framework.utils.StringUtils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @author SJG
 *         2018/1/8.
 */
@Slf4j
@Service
public class SmsService implements ISmsService {
    private static final String VERIFY_CODE_ID = "sms_verify_code_";

    @Autowired
    private SmsProperties smsProperties;
    @Autowired
    private RedisClient redisClient;

    private ObjectMapper objectMapper = new ObjectMapper();

    /**
     * 发送短信
     *
     * @param mobile  手机号码
     * @param smsType 短信类型 com.danbay.cloud.base.constants.SmsType
     * @param data    数据
     * @return 短信流水号
     * @throws BusinessException 业务异常
     */
    public <T> String send(String mobile, SmsType smsType, T data) throws BusinessException {
        if (StringUtils.isBlank(mobile) || data == null) {
            throw new BusinessException("参数不正确");
        }

        String templateParam;
        try {
            templateParam = objectMapper.writeValueAsString(data);
        } catch (JsonProcessingException e) {
            throw new BusinessException("短信发送失败，序列化发送参数失败", e);
        }
        String templateCode = smsProperties.getTemplateCode(smsType);
        String outId = mobile + DateUtils.now(DateUtils.YMDHMS2);
        log.info("开始发送短信,mobile:{},templateCode:{},data:{},outId:{}", mobile, templateCode, templateParam, outId);

        //产品名称:云通信短信API产品,开发者无需替换
        String product = smsProperties.getProvider().getProduct();
        //产品域名,开发者无需替换
        String domain = smsProperties.getProvider().getDomain();

        //此处需要替换成开发者自己的AK(在阿里云访问控制台寻找)
        String accessKeyId = smsProperties.getProvider().getAccessKeyId();
        String accessKeySecret = smsProperties.getProvider().getAccessKeySecret();

        //可自助调整超时时间
        System.setProperty("sun.net.client.defaultConnectTimeout", String.valueOf(smsProperties.getProvider().getConnectTimeout()));
        System.setProperty("sun.net.client.defaultReadTimeout", String.valueOf(smsProperties.getProvider().getReadTimeout()));

        //初始化acsClient,暂不支持region化
        String regionId = smsProperties.getProvider().getRegionId();
        String endpointName = smsProperties.getProvider().getEndpointName();
        IClientProfile profile = DefaultProfile.getProfile(regionId, accessKeyId, accessKeySecret);
        try {
            DefaultProfile.addEndpoint(endpointName, regionId, product, domain);
        } catch (ClientException e) {
            throw new BusinessException("短信发送失败，添加发送端点失败", e);
        }
        IAcsClient acsClient = new DefaultAcsClient(profile);

        //组装请求对象-具体描述见控制台-文档部分内容
        SendSmsRequest request = new SendSmsRequest();
        //必填:待发送手机号
        request.setPhoneNumbers(mobile);
        //必填:短信签名-可在短信控制台中找到
        request.setSignName(smsProperties.getSignName());
        //必填:短信模板-可在短信控制台中找到
        request.setTemplateCode(templateCode);
        //可选:模板中的变量替换JSON串,如模板内容为"亲爱的${name},您的验证码为${code}"时,此处的值为
        request.setTemplateParam(templateParam);

        //选填-上行短信扩展码(无特殊需求用户请忽略此字段)
        //request.setSmsUpExtendCode("90997");

        //可选:outId为提供给业务方扩展字段,最终在短信回执消息中将此值带回给调用者
        request.setOutId(outId);

        //hint 此处可能会抛出异常，注意catch
        try {
            SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request);
            log.info("短信发送完成,bizId:{},code:{},message:{}", sendSmsResponse.getBizId(), sendSmsResponse.getCode(), sendSmsResponse.getMessage());
            if (sendSmsResponse.getCode() == null || !sendSmsResponse.getCode().equals("OK")) {
                throw new BusinessException("短信发送失败:" + sendSmsResponse.getMessage());
            }
        } catch (ClientException e) {
            throw new BusinessException("短信发送失败，获取发送结果失败", e);
        }

        return outId;
    }

    /**
     * 发送验证码短信
     *
     * @param mobile    手机号码
     * @param checkCode 验证码
     * @param expire    过期时间(秒)
     * @throws BusinessException 业务异常
     */
    @Override
    public String sendCheckCode(String mobile, String checkCode, Integer expire) throws BusinessException {
        String sid = send(mobile, SmsType.CHECK_CODE, new CheckCode(checkCode));
        redisClient.set(VERIFY_CODE_ID + sid, checkCode, expire);
        return sid;
    }

    /**
     * 发送找回密码验证码短信
     *
     * @param mobile    手机号码
     * @param checkCode 验证码
     * @param expire    过期时间(秒)
     * @return 验证码id
     * @throws BusinessException 业务异常
     */
    @Override
    public String sendFindPwdCheckCode(String mobile, String checkCode, Integer expire) throws BusinessException {
        String sid = send(mobile, SmsType.FIND_PWD_CHECK_CODE, new FindPwdCheckCodeDto(checkCode));
        redisClient.set(VERIFY_CODE_ID + sid, checkCode, expire);
        return sid;
    }

    /**
     * 发送租客临时密码短信
     *
     * @param mobile    手机号码
     * @param tenantPwd 租客密码
     * @throws BusinessException 业务异常
     */
    @Override
    public void sendTenantTempPwd(String mobile, TenantPwdDto tenantPwd) throws BusinessException {
        send(mobile, SmsType.TENANT_TEMP_PWD, tenantPwd);
    }

    /**
     * 发送租客密码短信
     *
     * @param mobile    手机号码
     * @param tenantPwd 租客密码
     * @throws BusinessException 业务异常
     */
    @Override
    public void sendTenantPwd(String mobile, TenantPwdDto tenantPwd) throws BusinessException {
        send(mobile, SmsType.TENANT_PWD, tenantPwd);
    }

    /**
     * 发送租客手机号变更通知短信
     *
     * @param tenantMobileChange 租客手机号变更通知
     * @throws BusinessException 业务异常
     */
    @Override
    public void sendTenantMobileChange(TenantMobileChangeDto tenantMobileChange) throws BusinessException {
        send(tenantMobileChange.getTel(), SmsType.TENANT_MOBILE_CHANGE, tenantMobileChange);
    }

    /**
     * 发送门锁临时密码短信
     *
     * @param mobile      手机号码
     * @param doorTempPwd 门锁临时密码
     * @throws BusinessException 业务异常
     */
    @Override
    public void sendDoorTempPwd(String mobile, DoorTempPwdDto doorTempPwd) throws BusinessException {
        send(mobile, SmsType.DOOR_TEMP_PWD, doorTempPwd);
    }

    /**
     * 发员工注册通知短信
     *
     * @param employeeRegNotice 员工注册通知
     * @throws BusinessException 业务异常
     */
    @Override
    public void sendEmployeeRegNotice(EmployeeRegNoticeDto employeeRegNotice) throws BusinessException {
        send(employeeRegNotice.getTel(), SmsType.EMPLOYEE_REG_NOTICE, employeeRegNotice);
    }

    /**
     * 发绑定账户通知短信
     *
     * @param employeeRegNotice 绑定账户通知
     * @throws BusinessException 业务异常
     */
    @Override
    public void sendBindAccountNotice(EmployeeRegNoticeDto employeeRegNotice) throws BusinessException {
        send(employeeRegNotice.getTel(), SmsType.BIND_ACCOUNT_NOTICE, employeeRegNotice);
    }

    /**
     * 发账号变更通知短信
     *
     * @param employeeRegNotice 账号变更通知
     * @throws BusinessException 业务异常
     */
    @Override
    public void sendAccountChangeNotice(EmployeeRegNoticeDto employeeRegNotice) throws BusinessException {
        send(employeeRegNotice.getTel(), SmsType.ACCOUNT_CHANGE_NOTICE, employeeRegNotice);
    }

    /**
     * 发注册通知短信
     *
     * @param employeeRegNotice 注册通知
     * @throws BusinessException 业务异常
     */
    @Override
    public void sendRegisterNotice(EmployeeRegNoticeDto employeeRegNotice) throws BusinessException {
        send(employeeRegNotice.getTel(), SmsType.REGISTER_NOTICE, employeeRegNotice);
    }

    /**
     * 验证短信验证码
     *
     * @param codeValidate 待验证的验证码信息
     * @return 是否正确
     */
    @Override
    public boolean validate(CodeValidateDto codeValidate) throws BusinessException {
        BeanValidator.valid(codeValidate);
        boolean valid = false;

        String key = VERIFY_CODE_ID + codeValidate.getId();
        if (redisClient.hasKey(key)) {
            String code = redisClient.get(key);
            valid = codeValidate.getCode().equalsIgnoreCase(code);
        }
        log.info("用户:{}请求校验id:{}短信验证码:{}结果:{}", codeValidate.getUserName(), codeValidate.getId(), codeValidate.getCode(), valid);
        return valid;
    }

    /**
     * 发送门锁密码短信
     *
     * @param mobile  手机号码
     * @param pwd     密码
     * @param begin   有效期开始时间
     * @param end     有效期结束时间
     * @param pwdName 密码名
     * @throws BusinessException 业务异常
     */
    @Override
    public void sendDoorPwd(String mobile, String pwd, String begin, String end, String pwdName) throws BusinessException {
        send(mobile, SmsType.DOOR_PWD, new DoorPwd(pwd, begin, end, pwdName));
    }

    /**
     * 验证码
     */
    @Data
    @AllArgsConstructor
    private static class CheckCode {
        private String authNo;
    }

    /**
     * 门锁密码
     */
    @Data
    @AllArgsConstructor
    private static class DoorPwd {
        String pwd;
        String begin;
        String end;
        String pwdName;
    }
}
